package com.example.gaojt.androiddemo.boostDemo.aa;

import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.os.Build;
import android.support.v4.util.ArrayMap;
import android.text.TextUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

public class BoostScanTask {
    static final int MAX_SCAN_SERVICE = 256;
    private Context mContext;
    private ArrayList<ProcBaseFilter> mFilters = new ArrayList<ProcBaseFilter>();

    public BoostScanTask(Context ctx) {
        mContext = ctx;
        ProcNoCleanFilter noCleanFilter = new ProcNoCleanFilter(ctx);
        mFilters.add(noCleanFilter);
        // for non-quick scan, we need to doFilter many cases
        ProcAdviceKeepFilter adviceKeepFilter = new ProcAdviceKeepFilter(ctx);
        ProcServiceFilter serviceFilter = new ProcServiceFilter(ctx);

        mFilters.add(adviceKeepFilter);
        mFilters.add(serviceFilter);
    }

    /*
 * Scan task callback interface
 */
    public interface IScanTaskCallback {

        void onScanError();

        void onScanProgress(ProcessModel data);

        void onScanPreFinish(BoostResult results);

        void onScanFinish(BoostResult results);
    }


    public void scan(IScanTaskCallback callback) {
        try {
            scanInternal(callback);
        } catch (Exception e) {}
    }

    private void scanInternal(IScanTaskCallback callback) {

        ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> processList = ProcessManager.getRunningAppProcessInfo(mContext);

        if (processList == null) {
            // error handling
            callback.onScanError();
            return;
        }

        ArrayList<ProcessModel> results = new ArrayList<>();
        ArrayList<ProcessModel.ProcessInfo> procInfoList = createProcInfoList(processList);
        ArrayMap<String, ProcessModel> procMap = createProcModelMap(procInfoList);
        if (procMap != null && procMap.size() > 0) {
            List<ActivityManager.RunningServiceInfo> svcList = am.getRunningServices(MAX_SCAN_SERVICE);

            Collection<ProcessModel> procList = procMap.values();

            for (ProcessModel pm : procList) {
                if (!pm.mIsHide) {
                    addServiceInfo(svcList, pm);

                    if (callback != null) {
                        callback.onScanProgress(pm);
                    }
                }
            }

            results.addAll(procMap.values());
        }

        BoostResult procResult = new BoostResult();
        procResult.updateData(results);
        // pre-finish callback for fast response
        if (callback != null) {
            callback.onScanPreFinish(procResult);
        }

        // heavy weight scanning
        for (ProcessModel pm : results) {
            if (!pm.mIsHide) {
                updateOptionInfo(am, pm);
            }
        }
        // final callback for completed results
        if (callback != null) {
            callback.onScanFinish(procResult);
        }
    }

    private ArrayList<ProcessModel.ProcessInfo> createProcInfoList(
            List<ActivityManager.RunningAppProcessInfo> processList) {
        ArrayList<ProcessModel.ProcessInfo> procInfoList = new ArrayList<>();
        int myUid = mContext.getApplicationInfo().uid;

        for (ActivityManager.RunningAppProcessInfo rpi : processList) {
            if (rpi.uid == myUid) {
                continue;
            }
            ProcessModel.ProcessInfo procInfo = new ProcessModel.ProcessInfo();
            procInfo.mPid = rpi.pid;
            procInfo.mUID = rpi.uid;
            procInfo.mProcessName = rpi.processName;
            procInfo.mImportance = rpi.importance;
            procInfo.mOOM = ProcessOOMHelper.getProcessOOM(rpi.pid);
            if (rpi.pkgList != null) {
                procInfo.mPkgList.addAll(Arrays.asList(rpi.pkgList));
            }

            for (ProcBaseFilter filter : mFilters) {
                filter.doFilter(procInfo);
            }

            procInfoList.add(procInfo);

        }

        updateDependency(procInfoList);

        return procInfoList;
    }

    private void updateDependency(ArrayList<ProcessModel.ProcessInfo> procInfoList) {
        ArrayList<ProcessModel.ProcessInfo> checkList = new ArrayList<>(procInfoList);

        for (ProcessModel.ProcessInfo procInfo : procInfoList) {
            unionByUidAndPackage(procInfo, checkList);
        }
    }

    private void unionByUidAndPackage(ProcessModel.ProcessInfo procInfo,
            ArrayList<ProcessModel.ProcessInfo> checkList) {
        boolean needCheckUid = Build.VERSION.SDK_INT < 17;
        boolean needCheckPkg = procInfo.mPkgList.size() > 0;

        ArrayList<ProcessModel.ProcessInfo> unionList = new ArrayList<>();
        unionList.add(procInfo);

        int count = checkList.size();
        for (int i = count - 1; i >= 0; i--) {
            ProcessModel.ProcessInfo checkInfo = checkList.get(i);

            if (procInfo.mPid == checkInfo.mPid) {
                checkList.remove(i);
                continue;
            }

            boolean needUnion = false;

            if (needCheckUid && checkInfo.mUID == procInfo.mUID) {
                needUnion = true;
            }

            if (!needUnion && needCheckPkg) {
                for (String pkgName : procInfo.mPkgList) {
                    for (String checkPkg : checkInfo.mPkgList) {
                        if (pkgName.equals(checkPkg)) {
                            needUnion = true;
                            break;
                        }
                    }
                }
            }

            if (needUnion) {
                unionList.add(checkInfo);
                checkList.remove(i);
            }
        }

        unionCleanParameter(unionList);
    }

    private void unionCleanParameter(ArrayList<ProcessModel.ProcessInfo> unionList) {
        int maxSuggest = 0;
        int maxStrategy = 0;

        // find max suggest and strategy
        for (ProcessModel.ProcessInfo info : unionList) {
            if (info.mCleanSuggest > maxSuggest) {
                maxSuggest = info.mCleanSuggest;
            }

            if (info.mCleanStrategy > maxStrategy) {
                maxStrategy = info.mCleanStrategy;
            }
        }

        // Need check uid dependency
        for (ProcessModel.ProcessInfo info : unionList) {
            if (info.mCleanSuggest < maxSuggest) {
                ProcessModel.ProcessAdvInfo adv = new ProcessModel.ProcessAdvInfo();
                adv.mDescription = ProcessModel.ProcessAdvInfo.DEPEND_UID;
                adv.mStatus = 1;
                info.mAdvanceInfo.add(adv);
            }
            info.mCleanSuggest = maxSuggest;
            info.mCleanStrategy = maxStrategy;
        }
    }

    private ArrayMap<String, ProcessModel> createProcModelMap(final List<ProcessModel.ProcessInfo> src) {
        if (src == null || src.size() == 0) {
            return null;
        }

        ArrayMap<String, ProcessModel> procMap = new ArrayMap<>();
        for (ProcessModel.ProcessInfo pi : src) {
            ProcessModel pm = getProcessModel(procMap, pi);
            if (pm == null) {
                continue;
            }

            int storeState = 0;

            // White list
            if (ProcessWhiteListMarkHelper.isUserModified(storeState)) {
                // Apply user modified
                if (ProcessWhiteListMarkHelper.isUserWhiteList(storeState)) {
                    pm.mIsHide = true;
                } else {
                    pm.setCleanStrategy(pi.mCleanStrategy);
                    if (ProcessWhiteListMarkHelper.isUserUnchecked(storeState)) {
                        pm.setChecked(false);
                    } else if (ProcessWhiteListMarkHelper.isUserChecked(storeState)
                            || ProcessWhiteListMarkHelper.isUserUnwhite(storeState)) {
                        pm.setChecked(true);
                    } else {
                        if (pi.mCleanSuggest == ProcessModel.ProcessInfo.PROC_SUGGEST_DONT_CLEAN) {
                            pm.mIsHide = true;
                        } else {
                            pm.setChecked(pi.mCleanSuggest == ProcessModel.ProcessInfo.PROC_SUGGEST_CLEAN);
                        }
                    }
                }
            } else {
                // Apply auto rule
                if (pi.mCleanSuggest == ProcessModel.ProcessInfo.PROC_SUGGEST_DONT_CLEAN) {
                    pm.mIsHide = true;
                } else {
                    pm.setChecked(pi.mCleanSuggest == ProcessModel.ProcessInfo.PROC_SUGGEST_CLEAN);
                    pm.setCleanStrategy(pi.mCleanStrategy);
                }
            }

            // Advance info
            if (pi.mAdvanceInfo != null && pi.mAdvanceInfo.size() > 0) {
                for (ProcessModel.ProcessAdvInfo advInfo : pi.mAdvanceInfo) {
                    if (TextUtils.isEmpty(advInfo.mDescription)) {
                        continue;
                    }

                    if (advInfo.mDescription.equals(ProcessModel.ProcessAdvInfo.ACCOUT)) {
                        if (advInfo.mStatus == AccountScanner.ACCOUNT_LOGOUT) {
                            pm.setNeedCheckFlexibleWhiteList(true, advInfo.mStatus);
                        }
                    } else if (advInfo.mDescription.equals(ProcessModel.ProcessAdvInfo.UNUESD_SERVICE)) {
                        if (advInfo.mStatus == 1) {
                            pm.setNeedCheckFlexibleWhiteList(true);
                        }
                    } else if (advInfo.mDescription.equals(ProcessModel.ProcessAdvInfo.ABNORMAL_MEMORY)) {
                        pm.setAbnormal(true);
                    } else if (advInfo.mDescription.equals(ProcessModel.ProcessAdvInfo.CLOUD_CONTROL)) {
                        pm.setMemoryCheckEx(true);
                    } else if (advInfo.mDescription.equals(ProcessModel.ProcessAdvInfo.DEFAULT_NOT_CLEAN)
                            || advInfo.mDescription.equals(ProcessModel.ProcessAdvInfo.ADVICE_KEEP)) {
                        storeState |= ProcessWhiteListMarkHelper.FLAG_WHITELIST;
                        if (advInfo.mDescription.equals(ProcessModel.ProcessAdvInfo.ADVICE_KEEP)) {
                            pm.setKeepReasion(advInfo.mStatus);
                        }
                    } else if (advInfo.mDescription.equals(ProcessModel.ProcessAdvInfo.SOCIAL_PROCESS)) {
                        pm.setExtKillStrategy(ProcessModel.ProcScanResult.STRATEGY_KILL);
                    } else if (advInfo.mDescription.equals(ProcessModel.ProcessAdvInfo.DEPEND_UID)) {
                        pm.setDependUid(advInfo.mStatus == 1);
                    }
                }
            }

            pm.setIgnoreMark(storeState);

        }

        return procMap;
    }

    private ProcessModel getProcessModel(ArrayMap<String, ProcessModel> currentMap, ProcessModel.ProcessInfo pi) {
        // check parameters first
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            if (currentMap == null || pi == null || pi.mPkgList == null || pi.mPkgList.size() <= 0) {
                return null;
            }

        }
        String key = pi.mPkgList.get(0);
        ProcessModel pm = currentMap.get(key);

        if (pm == null) {
            // This pm is not in procMap, so we creata a new one
            pm = new ProcessModel();
            pm.setPkgName(key);
            pm.setUid(pi.mUID);
            try {
                PackageManager pmgr = mContext.getPackageManager();
                PackageInfo info = mContext.getPackageManager().getPackageInfo(key, 0);

                if (info != null) {
                    if (PackageUtils.isCoreApp(info) || PackageUtils.isPersistentApp(info)) {
                        return null;
                    }

                    ApplicationInfo appInfo = info.applicationInfo;
                    try {
                        if (appInfo != null) {
                            CharSequence labelCS = appInfo.loadLabel(pmgr);
                            if (labelCS != null) {
                                String labelStr = labelCS.toString();
                                pm.setTitle(labelStr);
                                if (labelStr.startsWith("com.")) {
                                    pm.setHasLabel(false);
                                }

                            }

                        }
                        if (appInfo.labelRes == 0) {
                            pm.setHasLabel(false);
                        }
                    } catch (Resources.NotFoundException ex) {

                    }
                }
            } catch (PackageManager.NameNotFoundException nnfe) {
                pm.setHasLabel(false);
                return null;// 说明这是一个进程，不是一个应用
            } catch (Exception e) {
                return null;
            }

            // pm.setTitle(LabelNameUtil.getInstance().getLabelNameOut(key, null));

            ApplicationInfo ai = PackageUtils.getApplicationInfo(mContext, key);
            if (ai != null) {
                pm.setAppFlags(ai.flags);
                pm.mType = ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0)
                        ? ProcessModel.PROCESS_SYS
                        : ProcessModel.PROCESS_USER;
            }

            currentMap.put(key, pm);
        }

        // update mPid, mOOM and inner object
        pm.addPid(pi.mPid);
        pm.addOOM(pi.mOOM);

        return pm;
    }

    private void addServiceInfo(List<ActivityManager.RunningServiceInfo> svcList, ProcessModel pm) {
        if (svcList != null && pm != null) {
            int svcCount = 0;
            long lastActivityTime = 0;

            for (ActivityManager.RunningServiceInfo svc : svcList) {
                if (svc.service.getPackageName().equals(pm.getPkgName())) {
                    svcCount++;
                    lastActivityTime = svc.lastActivityTime;

                    ServiceInfo svcInfo = null;
                    try {
                        svcInfo = mContext.getPackageManager().getServiceInfo(svc.service, 0);
                    } catch (Exception e) {}

                    // serviceInfo.service.
                    if (svc.foreground && svcInfo != null) {
                        if (svcInfo.exported && TextUtils.isEmpty(svcInfo.permission)) {
                            pm.addServComponent(svc.service);
                        } else {
                            pm.setKillLevel(ProcessModel.KILL_LEVEL.WITH_ROOT);
                        }
                    }

                }
            }

            pm.setServicesCount(svcCount);
            pm.setRecentestlastActivityTime(lastActivityTime);
        }
    }

    private void updateOptionInfo(ActivityManager am, ProcessModel pm) {
        long memory = MemoryInfoHelper.getProcessMemory(am, pm.getPidList());
        pm.setMemory(memory);
    }

}
